/*
 * Copyright (C) 2005-2011 MaNGOS <http://www.getmangos.com/>
 *
 * Copyright (C) 2008-2011 Trinity <http://www.trinitycore.org/>
 *
 * Copyright (C) 2010-2011 ProjectSkyfire <http://www.projectskyfire.org/>
 * 
 * Copyright (C) 2011 ArkCORE <http://www.arkania.net/>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 */

#include "gamePCH.h"
#include "WorldSession.h"
#include "WorldPacket.h"
#include "DBCStores.h"
#include "Player.h"
#include "Group.h"
#include "LFGMgr.h"
#include "ObjectMgr.h"
#include "InstanceScript.h"
#include "Group.h"
#include "GameEventMgr.h"

void BuildPlayerLockDungeonBlock(WorldPacket& data, const LfgLockMap& lock) {
	data << uint32(lock.size()); // Size of lock dungeons
	for (LfgLockMap::const_iterator it = lock.begin(); it != lock.end(); ++it) {
		data << uint32(it->first); // Dungeon entry (id + type)
		data << uint32(it->second); // Lock status
	}
}

void BuildPartyLockDungeonBlock(WorldPacket& data,
		const LfgLockPartyMap& lockMap) {
	data << uint8(lockMap.size());
	for (LfgLockPartyMap::const_iterator it = lockMap.begin();
			it != lockMap.end(); ++it) {
		data << uint64(it->first); // Player guid
		BuildPlayerLockDungeonBlock(data, it->second);
	}
}

void WorldSession::HandleLfgJoinOpcode(WorldPacket& recv_data) {
	if (!sWorld->getBoolConfig(CONFIG_DUNGEON_FINDER_ENABLE)
			|| (GetPlayer()->GetGroup()
					&& GetPlayer()->GetGroup()->GetLeaderGUID()
							!= GetPlayer()->GetGUID())) {
		recv_data.rpos(recv_data.wpos());
		return;
	}

	uint8 numDungeons;
	uint32 dungeon;
	uint32 roles;

	recv_data >> roles;
	recv_data.read_skip<uint16>(); // uint8 (always 0) - uint8 (always 0)
	recv_data >> numDungeons;
	if (!numDungeons) {
		sLog->outDebug(LOG_FILTER_NETWORKIO,
				"CMSG_LFG_JOIN [" UI64FMTD "] no dungeons selected",
				GetPlayer()->GetGUID());
		recv_data.rpos(recv_data.wpos());
		return;
	}

	LfgDungeonSet newDungeons;
	for (int8 i = 0; i < numDungeons; ++i) {
		recv_data >> dungeon;
		newDungeons.insert((dungeon & 0x00FFFFFF)); // remove the type from the dungeon entry
	}

	recv_data.read_skip<uint32>(); // for 0..uint8 (always 3) { uint8 (always 0) }

	std::string comment;
	recv_data >> comment;
	sLog->outDebug(LOG_FILTER_NETWORKIO,
			"CMSG_LFG_JOIN [" UI64FMTD "] roles: %u, Dungeons: %u, Comment: %s",
			GetPlayer()->GetGUID(), roles, uint8(newDungeons.size()),
			comment.c_str());
	sLFGMgr->Join(GetPlayer(), uint8(roles), newDungeons, comment);
}

void WorldSession::HandleLfgLeaveOpcode(WorldPacket& /*recv_data*/) {
	Group* grp = GetPlayer()->GetGroup();

	sLog->outDebug(LOG_FILTER_NETWORKIO,
			"CMSG_LFG_LEAVE [" UI64FMTD "] in group: %u",
			GetPlayer()->GetGUID(), grp ? 1 : 0);

	// Check cheating - only leader can leave the queue
	if (!grp || grp->GetLeaderGUID() == GetPlayer()->GetGUID())
		sLFGMgr->Leave(GetPlayer(), grp);
}

void WorldSession::HandleLfgProposalResultOpcode(WorldPacket& recv_data) {
	uint32 lfgGroupID; // Internal lfgGroupID
	bool accept; // Accept to join?
	recv_data >> lfgGroupID;
	recv_data >> accept;

	sLog->outDebug(LOG_FILTER_NETWORKIO,
			"CMSG_LFG_PROPOSAL_RESULT [" UI64FMTD "] proposal: %u accept: %u",
			GetPlayer()->GetGUID(), lfgGroupID, accept ? 1 : 0);
	sLFGMgr->UpdateProposal(lfgGroupID, GetPlayer()->GetGUID(), accept);
}

void WorldSession::HandleLfgSetRolesOpcode(WorldPacket& recv_data) {
	uint8 roles;
	recv_data >> roles; // Player Group Roles
	uint64 guid = GetPlayer()->GetGUID();
	Group* grp = GetPlayer()->GetGroup();
	if (!grp) {
		sLog->outDebug(LOG_FILTER_NETWORKIO,
				"CMSG_LFG_SET_ROLES [" UI64FMTD "] Not in group", guid);
		return;
	}
	uint64 gguid = grp->GetGUID();
	sLog->outDebug(
			LOG_FILTER_NETWORKIO,
			"CMSG_LFG_SET_ROLES: Group [" UI64FMTD "], Player [" UI64FMTD "], Roles: %u",
			gguid, guid, roles);
	sLFGMgr->UpdateRoleCheck(gguid, guid, roles);
}

void WorldSession::HandleLfgSetCommentOpcode(WorldPacket& recv_data) {
	std::string comment;
	recv_data >> comment;
	uint64 guid = GetPlayer()->GetGUID();
	sLog->outDebug(LOG_FILTER_NETWORKIO,
			"CMSG_SET_LFG_COMMENT [" UI64FMTD "] comment: %s", guid,
			comment.c_str());

	sLFGMgr->SetComment(guid, comment);
}

void WorldSession::HandleLfgSetBootVoteOpcode(WorldPacket& recv_data) {
	bool agree; // Agree to kick player
	recv_data >> agree;

	sLog->outDebug(LOG_FILTER_NETWORKIO,
			"CMSG_LFG_SET_BOOT_VOTE [" UI64FMTD "] agree: %u",
			GetPlayer()->GetGUID(), agree ? 1 : 0);
	sLFGMgr->UpdateBoot(GetPlayer(), agree);
}

void WorldSession::HandleLfgTeleportOpcode(WorldPacket& recv_data) {
	bool out;
	recv_data >> out;

	sLog->outDebug(LOG_FILTER_NETWORKIO,
			"CMSG_LFG_TELEPORT [" UI64FMTD "] out: %u", GetPlayer()->GetGUID(),
			out ? 1 : 0);
	sLFGMgr->TeleportPlayer(GetPlayer(), out, true);
}

void WorldSession::HandleLfgPlayerLockInfoRequestOpcode(
		WorldPacket& /*recv_data*/) {
	uint64 guid = GetPlayer()->GetGUID();
	sLog->outDebug(LOG_FILTER_NETWORKIO,
			"CMSG_LFD_PLAYER_LOCK_INFO_REQUEST [" UI64FMTD "]", guid);

	// Get Random dungeons that can be done at a certain level and expansion
	// FIXME - Should return seasonals (when not disabled)
	LfgDungeonSet randomDungeons;
	uint8 level = GetPlayer()->getLevel();
	uint8 expansion = GetPlayer()->GetSession()->Expansion();
	for (uint32 i = 0; i < sLFGDungeonStore.GetNumRows(); ++i) {
		LFGDungeonEntry const* dungeon = sLFGDungeonStore.LookupEntry(i);
		if (dungeon && dungeon->type == LFG_TYPE_RANDOM
				&& dungeon->expansion <= expansion && dungeon->minlevel <= level
				&& level <= dungeon->maxlevel)
			randomDungeons.insert(dungeon->Entry());
		// Dungeons Seleccionables con el evento en el server correspondiente. (En Dungeon Finder)
		if (dungeon && dungeon->grouptype == 11
				&& dungeon->expansion <= expansion && dungeon->minlevel <= level
				&& level <= dungeon->maxlevel) {
			QueryResult result = WorldDatabase.Query(
					"SELECT dungeonId, eventEntry FROM lfg_dungeon_event");

			if (!result)
				return;

			Field* fields = NULL;
			do {
				fields = result->Fetch();
				uint32 dungeonId = fields[0].GetUInt32();
				uint32 eventEntry = fields[1].GetUInt32();
				if (dungeonId != dungeon->ID)
					continue;

				if (eventEntry && sGameEventMgr->IsActiveEvent(eventEntry))
					randomDungeons.insert(dungeon->Entry());
			} while (result->NextRow());
		}
	}

	// Get player locked Dungeons
	LfgLockMap lock = sLFGMgr->GetLockedDungeons(guid);
	uint32 rsize = uint32(randomDungeons.size());
	uint32 lsize = uint32(lock.size());

	sLog->outDebug(LOG_FILTER_NETWORKIO, "SMSG_LFG_PLAYER_INFO [" UI64FMTD "]",
			guid);
	WorldPacket data(
			SMSG_LFG_PLAYER_INFO,
			1 + rsize * (4 + 1 + 4 + 4 + 4 + 4 + 1 + 4 + 4 + 4) + 4
					+ lsize * (1 + 4 + 4 + 4 + 4 + 1 + 4 + 4 + 4));

	data << uint8(randomDungeons.size()); // Random Dungeon count
	for (LfgDungeonSet::const_iterator it = randomDungeons.begin();
			it != randomDungeons.end(); ++it) {
		data << uint32(*it); // Dungeon Entry (id + type)
		LfgReward const* reward = sLFGMgr->GetRandomDungeonReward(*it, level);
		Quest const* qRew = NULL;
		uint8 done = 0;
		if (reward) {
			qRew = sObjectMgr->GetQuestTemplate(reward->reward[0].questId);
			if (qRew) {
				done = !GetPlayer()->CanRewardQuest(qRew, false);
				if (done)
					qRew = sObjectMgr->GetQuestTemplate(
							reward->reward[1].questId);
			}
		}
		if (qRew) {
			data << uint8(done);
			data << uint32(qRew->GetRewOrReqMoney());
			data << uint32(qRew->XPValue(GetPlayer()));
			data << uint32(reward->reward[done].variableMoney);
			data << uint32(reward->reward[done].variableXP);
			data << uint8(qRew->GetRewItemsCount());
			if (qRew->GetRewItemsCount()) {
				ItemPrototype const* iProto = NULL;
				for (uint8 i = 0; i < QUEST_REWARDS_COUNT; ++i) {
					if (!qRew->RewItemId[i])
						continue;

					iProto = ObjectMgr::GetItemPrototype(qRew->RewItemId[i]);

					data << uint32(qRew->RewItemId[i]);
					data << uint32(iProto ? iProto->DisplayInfoID : 0);
					data << uint32(qRew->RewItemCount[i]);
				}
			}
		} else {
			data << uint8(0);
			data << uint32(0);
			data << uint32(0);
			data << uint32(0);
			data << uint32(0);
			data << uint8(0);
		}
	}
	BuildPlayerLockDungeonBlock(data, lock);
	SendPacket(&data);
}

void WorldSession::HandleLfgPartyLockInfoRequestOpcode(
		WorldPacket& /*recv_data*/) {
	uint64 guid = GetPlayer()->GetGUID();
	sLog->outDebug(LOG_FILTER_NETWORKIO,
			"CMSG_LFD_PARTY_LOCK_INFO_REQUEST [" UI64FMTD "]", guid);

	Group* grp = GetPlayer()->GetGroup();
	if (!grp)
		return;

	// Get the locked dungeons of the other party members
	LfgLockPartyMap lockMap;
	for (GroupReference* itr = grp->GetFirstMember(); itr != NULL;
			itr = itr->next()) {
		Player* plrg = itr->getSource();
		if (!plrg)
			continue;

		uint64 pguid = plrg->GetGUID();
		if (pguid == guid)
			continue;

		lockMap[pguid] = sLFGMgr->GetLockedDungeons(pguid);
	}

	uint32 size = 0;
	for (LfgLockPartyMap::const_iterator it = lockMap.begin();
			it != lockMap.end(); ++it)
		size += 8 + 4 + uint32(it->second.size()) * (4 + 4);

	sLog->outDebug(LOG_FILTER_NETWORKIO, "SMSG_LFG_PARTY_INFO [" UI64FMTD "]",
			guid);
	WorldPacket data(SMSG_LFG_PARTY_INFO, 1 + size);
	BuildPartyLockDungeonBlock(data, lockMap);
	SendPacket(&data);
}

void WorldSession::HandleLfrSearchOpcode(WorldPacket& recv_data) {
	uint32 entry; // Raid id to search
	recv_data >> entry;
	sLog->outDebug(LOG_FILTER_NETWORKIO,
			"CMSG_SEARCH_LFG_JOIN [" UI64FMTD "] dungeon entry: %u",
			GetPlayer()->GetGUID(), entry);
	//SendLfrUpdateListOpcode(entry);
}

void WorldSession::HandleLfrLeaveOpcode(WorldPacket& recv_data) {
	uint32 dungeonId; // Raid id queue to leave
	recv_data >> dungeonId;
	sLog->outDebug(LOG_FILTER_NETWORKIO,
			"CMSG_SEARCH_LFG_LEAVE [" UI64FMTD "] dungeonId: %u",
			GetPlayer()->GetGUID(), dungeonId);
	//sLFGMgr->LeaveLfr(GetPlayer(), dungeonId);
}

void WorldSession::SendLfgUpdatePlayer(const LfgUpdateData& updateData) {
	bool queued = false;
	bool extrainfo = false;

	switch (updateData.updateType) {
	case LFG_UPDATETYPE_JOIN_PROPOSAL:
	case LFG_UPDATETYPE_ADDED_TO_QUEUE:
		queued = true;
		extrainfo = true;
		break;
		//case LFG_UPDATETYPE_CLEAR_LOCK_LIST: // TODO: Sometimes has extrainfo - Check ocurrences...
	case LFG_UPDATETYPE_PROPOSAL_BEGIN:
		extrainfo = true;
		break;
	default:
		break;
	}

	uint64 guid = GetPlayer()->GetGUID();
	uint8 size = uint8(updateData.dungeons.size());

	sLog->outDebug(LOG_FILTER_NETWORKIO,
			"SMSG_LFG_UPDATE_PLAYER [" UI64FMTD "] updatetype: %u", guid,
			updateData.updateType);
	WorldPacket data(
			SMSG_LFG_UPDATE_PLAYER,
			1 + 1
					+ (extrainfo ? 1 : 0)
							* (1 + 1 + 1 + 1 + size * 4
									+ updateData.comment.length()));
	data << uint8(updateData.updateType); // Lfg Update type
	data << uint8(extrainfo); // Extra info
	if (extrainfo) {
		data << uint8(queued); // Join the queue
		data << uint8(0); // unk - Always 0
		data << uint8(0); // unk - Always 0

		data << uint8(size);
		if (size)
			for (LfgDungeonSet::const_iterator it = updateData.dungeons.begin();
					it != updateData.dungeons.end(); ++it)
				data << uint32(*it);
		data << updateData.comment;
	}
	SendPacket(&data);
}

void WorldSession::SendLfgUpdateParty(const LfgUpdateData& updateData) {
	bool join = false;
	bool extrainfo = false;
	bool queued = false;

	switch (updateData.updateType) {
	case LFG_UPDATETYPE_JOIN_PROPOSAL:
		extrainfo = true;
		break;
	case LFG_UPDATETYPE_ADDED_TO_QUEUE:
		extrainfo = true;
		join = true;
		queued = true;
		break;
	case LFG_UPDATETYPE_CLEAR_LOCK_LIST:
		// join = true;  // TODO: Sometimes queued and extrainfo - Check ocurrences...
		queued = true;
		break;
	case LFG_UPDATETYPE_PROPOSAL_BEGIN:
		extrainfo = true;
		join = true;
		break;
	default:
		break;
	}

	uint64 guid = GetPlayer()->GetGUID();
	uint8 size = uint8(updateData.dungeons.size());

	sLog->outDebug(LOG_FILTER_NETWORKIO,
			"SMSG_LFG_UPDATE_PARTY [" UI64FMTD "] updatetype: %u", guid,
			updateData.updateType);
	WorldPacket data(
			SMSG_LFG_UPDATE_PARTY,
			1 + 1
					+ (extrainfo ? 1 : 0)
							* (1 + 1 + 1 + 1 + 1 + size * 4
									+ updateData.comment.length()));
	data << uint8(updateData.updateType); // Lfg Update type
	data << uint8(extrainfo); // Extra info
	if (extrainfo) {
		data << uint8(join); // LFG Join
		data << uint8(queued); // Join the queue
		data << uint8(0); // unk - Always 0
		data << uint8(0); // unk - Always 0
		for (uint8 i = 0; i < 3; ++i)
			data << uint8(0); // unk - Always 0

		data << uint8(size);
		if (size)
			for (LfgDungeonSet::const_iterator it = updateData.dungeons.begin();
					it != updateData.dungeons.end(); ++it)
				data << uint32(*it);
		data << updateData.comment;
	}
	SendPacket(&data);
}

void WorldSession::SendLfgRoleChosen(uint64 guid, uint8 roles) {
	sLog->outDebug(
			LOG_FILTER_NETWORKIO,
			"SMSG_LFG_ROLE_CHOSEN [" UI64FMTD "] guid: [" UI64FMTD "] roles: %u",
			GetPlayer()->GetGUID(), guid, roles);

	WorldPacket data(SMSG_LFG_ROLE_CHOSEN, 8 + 1 + 4);
	data << uint64(guid); // Guid
	data << uint8(roles > 0); // Ready
	data << uint32(roles); // Roles
	SendPacket(&data);
}

void WorldSession::SendLfgRoleCheckUpdate(const LfgRoleCheck* pRoleCheck) {
	ASSERT(pRoleCheck);
	LfgDungeonSet dungeons;
	if (pRoleCheck->rDungeonId)
		dungeons.insert(pRoleCheck->rDungeonId);
	else
		dungeons = pRoleCheck->dungeons;

	sLog->outDebug(LOG_FILTER_NETWORKIO,
			"SMSG_LFG_ROLE_CHECK_UPDATE [" UI64FMTD "]",
			GetPlayer()->GetGUID());
	WorldPacket data(
			SMSG_LFG_ROLE_CHECK_UPDATE,
			4 + 1 + 1 + dungeons.size() * 4 + 1
					+ pRoleCheck->roles.size() * (8 + 1 + 4 + 1));

	data << uint32(pRoleCheck->state); // Check result
	data << uint8(pRoleCheck->state == LFG_ROLECHECK_INITIALITING);
	data << uint8(dungeons.size()); // Number of dungeons
	if (dungeons.size()) {
		for (LfgDungeonSet::iterator it = dungeons.begin();
				it != dungeons.end(); ++it) {
			LFGDungeonEntry const* dungeon = sLFGDungeonStore.LookupEntry(*it);
			data << uint32(dungeon ? dungeon->Entry() : 0); // Dungeon
		}
	}

	data << uint8(pRoleCheck->roles.size()); // Players in group
	if (pRoleCheck->roles.size()) {
		// Leader info MUST be sent 1st :S
		uint64 guid = pRoleCheck->leader;
		uint8 roles = pRoleCheck->roles.find(guid)->second;
		data << uint64(guid); // Guid
		data << uint8(roles > 0); // Ready
		data << uint32(roles); // Roles
		Player* plr = sObjectMgr->GetPlayer(guid);
		data << uint8(plr ? plr->getLevel() : 0); // Level

		for (LfgRolesMap::const_iterator it = pRoleCheck->roles.begin();
				it != pRoleCheck->roles.end(); ++it) {
			if (it->first == pRoleCheck->leader)
				continue;

			guid = it->first;
			roles = it->second;
			data << uint64(guid); // Guid
			data << uint8(roles > 0); // Ready
			data << uint32(roles); // Roles
			plr = sObjectMgr->GetPlayer(guid);
			data << uint8(plr ? plr->getLevel() : 0); // Level
		}
	}
	SendPacket(&data);
}

void WorldSession::SendLfgJoinResult(const LfgJoinResultData& joinData) {
	uint32 size = 0;
	for (LfgLockPartyMap::const_iterator it = joinData.lockmap.begin();
			it != joinData.lockmap.end(); ++it)
		size += 8 + 4 + uint32(it->second.size()) * (4 + 4);

	sLog->outDebug(
			LOG_FILTER_NETWORKIO,
			"SMSG_LFG_JOIN_RESULT [" UI64FMTD "] checkResult: %u checkValue: %u",
			GetPlayer()->GetGUID(), joinData.result, joinData.state);
	WorldPacket data(SMSG_LFG_JOIN_RESULT, 4 + 4 + size);
	data << uint32(joinData.result); // Check Result
	data << uint32(joinData.state); // Check Value
	if (!joinData.lockmap.empty())
		BuildPartyLockDungeonBlock(data, joinData.lockmap);
	SendPacket(&data);
}

void WorldSession::SendLfgQueueStatus(uint32 dungeon, int32 waitTime,
		int32 avgWaitTime, int32 waitTimeTanks, int32 waitTimeHealer,
		int32 waitTimeDps, uint32 queuedTime, uint8 tanks, uint8 healers,
		uint8 dps) {
	sLog->outDebug(
			LOG_FILTER_NETWORKIO,
			"SMSG_LFG_QUEUE_STATUS [" UI64FMTD "] dungeon: %u - waitTime: %d - avgWaitTime: %d - waitTimeTanks: %d - waitTimeHealer: %d - waitTimeDps: %d - queuedTime: %u - tanks: %u - healers: %u - dps: %u",
			GetPlayer()->GetGUID(), dungeon, waitTime, avgWaitTime,
			waitTimeTanks, waitTimeHealer, waitTimeDps, queuedTime, tanks,
			healers, dps);

	WorldPacket data(SMSG_LFG_QUEUE_STATUS,
			4 + 4 + 4 + 4 + 4 + 4 + 1 + 1 + 1 + 4);
	data << uint32(dungeon); // Dungeon
	data << int32(avgWaitTime); // Average Wait time
	data << int32(waitTime); // Wait Time
	data << int32(waitTimeTanks); // Wait Tanks
	data << int32(waitTimeHealer); // Wait Healers
	data << int32(waitTimeDps); // Wait Dps
	data << uint8(tanks); // Tanks needed
	data << uint8(healers); // Healers needed
	data << uint8(dps); // Dps needed
	data << uint32(queuedTime); // Player wait time in queue
	SendPacket(&data);
}

void WorldSession::SendLfgPlayerReward(uint32 rdungeonEntry,
		uint32 sdungeonEntry, uint8 done, const LfgReward* reward,
		const Quest* qRew) {
	if (!rdungeonEntry || !sdungeonEntry || !qRew)
		return;

	uint8 itemNum = uint8(qRew ? qRew->GetRewItemsCount() : 0);

	sLog->outDebug(
			LOG_FILTER_NETWORKIO,
			"SMSG_LFG_PLAYER_REWARD [" UI64FMTD "] rdungeonEntry: %u - sdungeonEntry: %u - done: %u",
			GetPlayer()->GetGUID(), rdungeonEntry, sdungeonEntry, done);
	WorldPacket data(SMSG_LFG_PLAYER_REWARD,
			4 + 4 + 1 + 4 + 4 + 4 + 4 + 4 + 1 + itemNum * (4 + 4 + 4));
	data << uint32(rdungeonEntry); // Random Dungeon Finished
	data << uint32(sdungeonEntry); // Dungeon Finished
	data << uint8(done);
	data << uint32(1);
	data << uint32(qRew->GetRewOrReqMoney());
	data << uint32(qRew->XPValue(GetPlayer()));
	data << uint32(reward->reward[done].variableMoney);
	data << uint32(reward->reward[done].variableXP);
	data << uint8(itemNum);
	if (itemNum) {
		ItemPrototype const* iProto = NULL;
		for (uint8 i = 0; i < QUEST_REWARDS_COUNT; ++i) {
			if (!qRew->RewItemId[i])
				continue;

			iProto = ObjectMgr::GetItemPrototype(qRew->RewItemId[i]);

			data << uint32(qRew->RewItemId[i]);
			data << uint32(iProto ? iProto->DisplayInfoID : 0);
			data << uint32(qRew->RewItemCount[i]);
		}
	}
	SendPacket(&data);
}

void WorldSession::SendLfgBootPlayer(const LfgPlayerBoot* pBoot) {
	uint64 guid = GetPlayer()->GetGUID();
	LfgAnswer playerVote = pBoot->votes.find(guid)->second;
	uint8 votesNum = 0;
	uint8 agreeNum = 0;
	uint32 secsleft = uint8((pBoot->cancelTime - time(NULL)) / 1000);
	for (LfgAnswerMap::const_iterator it = pBoot->votes.begin();
			it != pBoot->votes.end(); ++it) {
		if (it->second != LFG_ANSWER_PENDING) {
			++votesNum;
			if (it->second == LFG_ANSWER_AGREE)
				++agreeNum;
		}
	}
	sLog->outDebug(
			LOG_FILTER_NETWORKIO,
			"SMSG_LFG_BOOT_PROPOSAL_UPDATE [" UI64FMTD "] inProgress: %u - didVote: %u - agree: %u - victim: [" UI64FMTD "] votes: %u - agrees: %u - left: %u - needed: %u - reason %s",
			guid, uint8(pBoot->inProgress),
			uint8(playerVote != LFG_ANSWER_PENDING),
			uint8(playerVote == LFG_ANSWER_AGREE), pBoot->victim, votesNum,
			agreeNum, secsleft, pBoot->votedNeeded, pBoot->reason.c_str());
	WorldPacket data(SMSG_LFG_BOOT_PROPOSAL_UPDATE,
			1 + 1 + 1 + 8 + 4 + 4 + 4 + 4 + pBoot->reason.length());
	data << uint8(pBoot->inProgress); // Vote in progress
	data << uint8(playerVote != LFG_ANSWER_PENDING); // Did Vote
	data << uint8(playerVote == LFG_ANSWER_AGREE); // Agree
	data << uint64(pBoot->victim); // Victim GUID
	data << uint32(votesNum); // Total Votes
	data << uint32(agreeNum); // Agree Count
	data << uint32(secsleft); // Time Left
	data << uint32(pBoot->votedNeeded); // Needed Votes
	data << pBoot->reason.c_str(); // Kick reason
	SendPacket(&data);
}

void WorldSession::SendLfgUpdateProposal(uint32 proposalId,
		const LfgProposal* pProp) {
	if (!pProp)
		return;

	uint64 guid = GetPlayer()->GetGUID();
	LfgProposalPlayerMap::const_iterator itPlayer = pProp->players.find(guid);
	if (itPlayer == pProp->players.end()) // Player MUST be in the proposal
		return;

	LfgProposalPlayer* ppPlayer = itPlayer->second;
	uint32 pLowGroupGuid = ppPlayer->groupLowGuid;
	uint32 dLowGuid = pProp->groupLowGuid;
	uint32 dungeonId = pProp->dungeonId;
	bool isSameDungeon = false;
	bool isContinue = false;
	Group* grp = dLowGuid ? sObjectMgr->GetGroupByGUID(dLowGuid) : NULL;
	uint32 completedEncounters = 0;
	if (grp) {
		uint64 gguid = grp->GetGUID();
		isContinue = grp->isLFGGroup()
				&& sLFGMgr->GetState(gguid) != LFG_STATE_FINISHED_DUNGEON;
		isSameDungeon = GetPlayer()->GetGroup() == grp && isContinue;
	}

	sLog->outDebug(LOG_FILTER_NETWORKIO,
			"SMSG_LFG_PROPOSAL_UPDATE [" UI64FMTD "] state: %u",
			GetPlayer()->GetGUID(), pProp->state);
	WorldPacket data(
			SMSG_LFG_PROPOSAL_UPDATE,
			4 + 1 + 4 + 4 + 1 + 1
					+ pProp->players.size() * (4 + 1 + 1 + 1 + 1 + 1));

	if (!isContinue) // Only show proposal dungeon if it's continue
	{
		LfgDungeonSet playerDungeons = sLFGMgr->GetSelectedDungeons(guid);
		if (playerDungeons.size() == 1)
			dungeonId = (*playerDungeons.begin());
	}

	if (LFGDungeonEntry const* dungeon = sLFGDungeonStore.LookupEntry(dungeonId)) {
		dungeonId = dungeon->Entry();

		// Select a player inside to be get completed encounters from
		if (grp) {
			for (GroupReference* itr = grp->GetFirstMember(); itr != NULL; itr =
					itr->next()) {
				Player* groupMember = itr->getSource();
				if (groupMember
						&& groupMember->GetMapId() == uint32(dungeon->map)) {
					if (InstanceScript* instance = groupMember->GetInstanceScript())
						completedEncounters =
								instance->GetCompletedEncounterMask();
					break;
				}
			}
		}
	}

	data << uint32(dungeonId); // Dungeon
	data << uint8(pProp->state); // Result state
	data << uint32(proposalId); // Internal Proposal ID
	data << uint32(completedEncounters); // Bosses killed
	data << uint8(isSameDungeon); // Silent (show client window)
	data << uint8(pProp->players.size()); // Group size

	for (itPlayer = pProp->players.begin(); itPlayer != pProp->players.end();
			++itPlayer) {
		ppPlayer = itPlayer->second;
		data << uint32(ppPlayer->role); // Role
		data << uint8(itPlayer->first == guid); // Self player
		if (!ppPlayer->groupLowGuid) // Player not it a group
		{
			data << uint8(0); // Not in dungeon
			data << uint8(0); // Not same group
		} else {
			data << uint8(ppPlayer->groupLowGuid == dLowGuid); // In dungeon (silent)
			data << uint8(ppPlayer->groupLowGuid == pLowGroupGuid); // Same Group than player
		}
		data << uint8(ppPlayer->accept != LFG_ANSWER_PENDING); // Answered
		data << uint8(ppPlayer->accept == LFG_ANSWER_AGREE); // Accepted
	}
	SendPacket(&data);
}

void WorldSession::SendLfgUpdateSearch(bool update) {
	sLog->outDebug(LOG_FILTER_NETWORKIO,
			"SMSG_LFG_UPDATE_LIST [" UI64FMTD "] update: %u",
			GetPlayer()->GetGUID(), update ? 1 : 0);
	WorldPacket data(SMSG_LFG_UPDATE_LIST, 1);
	data << uint8(update); // In Lfg Queue?
	SendPacket(&data);
}

void WorldSession::SendLfgDisabled() {
	sLog->outDebug(LOG_FILTER_NETWORKIO, "SMSG_LFG_DISABLED [" UI64FMTD "]",
			GetPlayer()->GetGUID());
	WorldPacket data(SMSG_LFG_DISABLED, 0);
	SendPacket(&data);
}

void WorldSession::SendLfgOfferContinue(uint32 dungeonEntry) {
	sLog->outDebug(LOG_FILTER_NETWORKIO,
			"SMSG_LFG_OFFER_CONTINUE [" UI64FMTD "] dungeon entry: %u",
			GetPlayer()->GetGUID(), dungeonEntry);
	WorldPacket data(SMSG_LFG_OFFER_CONTINUE, 4);
	data << uint32(dungeonEntry);
	SendPacket(&data);
}

void WorldSession::SendLfgTeleportError(uint8 err) {
	sLog->outDebug(LOG_FILTER_NETWORKIO,
			"SMSG_LFG_TELEPORT_DENIED [" UI64FMTD "] reason: %u",
			GetPlayer()->GetGUID(), err);
	WorldPacket data(SMSG_LFG_TELEPORT_DENIED, 4);
	data << uint32(err); // Error
	SendPacket(&data);
}

/*
 void WorldSession::SendLfrUpdateListOpcode(uint32 dungeonEntry)
 {
 sLog->outDebug(LOG_FILTER_PACKETIO, "SMSG_UPDATE_LFG_LIST [" UI64FMTD "] dungeon entry: %u", GetPlayer()->GetGUID(), dungeonEntry);
 WorldPacket data(SMSG_UPDATE_LFG_LIST);
 SendPacket(&data);
 }
 */
